<?php
require_once('URI.class.php');
require_once('Qname.class.php');

/**
 * GraphIndex
 * a class for manipulating and converting associative arrays in the RDF/JSON structure
 * @package converters
 * @author Keith Alexander
 **/
class GraphIndex extends ConvertableObject
{

	/**
	 * value - a getter for the associative array that this object provides access to
	 * @param array | boolean $namespaces - if an array of prefix => namespaces is passed
	 * 										use them to make qnames instead of URIs * 
	 * 										for the predicate keys.
	 * @param string | boolean $separator - if a string is passed, it will be used as 
	 *										the qname separator
	 * @param boolean $just_values			flattens the values into a flat array of object values if true
	 * @return array
	 * @author Keith Alexander
	 **/
	public 	function value($namespaces=false,$separator=false, $just_values=false)
	{
		if(!$namespaces AND !$just_values) return $this->value;
		else
		{
			$resources = $this->value;
			foreach($resources as $uri => $properties)
			{
				foreach($properties as $p=>$objs)
				{
					$property = URI::to_qname($p, $namespaces, $separator)->value();
					$namespace = URI::to_namespace($p);
					if(!$just_values AND in_array($namespace, array_values($namespaces)))
					{
						$resources[$uri][$property] = $objs;
						unset($resources[$uri][$p]); //remove uri predicate key
						
					}
					else if($just_values)
					{
						unset($resources[$uri][$p]); //remove uri_predicate key
						if(!in_array($namespace, array_values($namespaces)))
						{
							$property = $p; //reset qname back to uri							
						}
						foreach($objs as $o)
						{
							$resources[$uri][$property][]=$o['value'];
						}

						
					}
					
				}
		
			}
			return $resources;
		}
	}
	
	/**
	 * has_resource
	 *
	 * @param string $uri - checks if a resource with that URI is present in the array
	 * @return boolean
	 * @author Keith Alexander
	 **/
	public function has_resource($uri)
	{
		$resources = $this->value();
		if(isset($resources[$uri])) return true;
		else return false;
	}
	
	/**
	 * find_by_rdftype
	 * returns a resources array filtered down to resources with a particular rdf:type value
	 * @param string $rdftype_uri 
	 * @return array
	 * @author Keith Alexander
	 **/
	public function find_by_rdftype($rdftype_uri)
	{
		$results =  $this->find_by_object_value($rdftype_uri, self::RDF.'type', true);
		return (isset($results[self::RDF.'type']))? $results[self::RDF.'type'] : false ;
	}
	
	/**
	 * find_by_object_value
	 * returns a resources array filtered according to the parameters passed
	 * @param string $object_value - the value to filter by
	 * @param string | boolean $property - the URI of the property to filter by
	 * @param string | boolean $get_value - determines whether to return 
	 * 										an array of objects or arrays
	 * @return array - the key is the property the value was found under, 
	 * 	the value is eitehr a resources array, or a ResourceArray object 
	 * @author Keith Alexander
	 **/
	public function find_by_object_value($object_value, $property=false, $get_value = false)
	{
		$resources = $this->value();
		if(empty($resources) or !is_array($resources)) return array();
		$results = array();
		foreach($resources as $uri => $properties)
		{
			foreach($properties as $prop => $objs)
			{
				if(!$property or ($prop==$property) )
				{
					foreach($objs as $o)
					{
						if(isset($o['value']) and $o['value'] == $object_value)
						{
 							if(!$get_value)
							{
								$results[$prop][] = $this->to_resource($uri);	
							}
							else
							{
								$results[$prop][$uri] = $properties;
							}
							break;
						}
					}
				}
			}
		}
		return $results;
	}
	
	/**
	 * find_related_resources
	 * @param string $object_value
	 * @deprecated 
	 * @return void
	 * @author Keith Alexander
	 **/
	public function find_related_resources($object_value)
	{
		return $this->find_by_object_value($object_value);
	}
	
	/**
	 * get_languages
	 * @param array | bool $resources - can be passed so the method can be called statically
	 * @return array - an array of language codes
	 * @author Keith Alexander
	 **/
	public function get_languages($resources=false)
	{
		$langs = array();
		if(!is_array($resources)) $resources = $this->value();
		foreach($resources as $uri => $props)
		{
			foreach($props as $p => $objs)
			{
				foreach($objs as $o)
				{
					if(!empty($o['lang'])) $langs[]=$o['lang'];
				}
			}
		}
		return array_unique($langs);
	}
	
	/**
	 * reify
	 *
	 * @return object
	 * @author Keith
	 **/
	public function reify($resources=false)
	{
		if(!$resources) $resources = $this->value();
		$reified = array();
		$statement_no = 1;
		foreach($resources as $uri => $properties){
			foreach($properties as $property => $objects){
				foreach($objects as $object){
					while(!isset($statement_nodeID) OR isset($resources[$statement_nodeID]))
					{
						$statement_nodeID = '_:Statement'.$statement_no++;
					}
					
					$reified[$statement_nodeID]= array(
						self::RDF.'type'=>array(
								array('type'=>'uri','value'=>self::RDF.'Statement')
									),
						self::RDF.'subject' => array(array('type'=>  (substr($uri,0,2)=='_:')? 'bnode' : 'uri', 'value'=>$uri)),
						self::RDF.'predicate' => array(array('type'=>'uri','value'=>$property)),
						self::RDF.'object' => array($object),
								);
					
				}
			}
		}
		
		return new GraphIndex($reified);
	}
	
	/**
	 * dereify
	 *
	 * @return object
	 * @author Keith
	 **/
	function dereify($resources=false)
	{
		if(!$resources) $resources = $this->value();

		foreach($resources as $uri => $properties)
		{
			if(
			isset($properties[self::RDF.'subject']) AND
			isset($properties[self::RDF.'predicate']) AND
			isset($properties[self::RDF.'object']) 
			)
			{
				$subject = $properties[self::RDF.'subject'][0]['value'];
				$property = $properties[self::RDF.'predicate'][0]['value'];
				$object = $properties[self::RDF.'object'][0];
				$resources[$subject][$property][]=$object;
				unset($resources[$uri]);
			}
		}
		return new GraphIndex($resources);
	}
	
	/**
	 * to_rdfxml
	 * 
	 * 
	 * @param array | bool $resources
	 * @param array | bool $namespaces
	 * @return string
	 * @author Keith Alexander
	 **/
	public function to_rdfxml($resources=false, $namespaces=false)
	{
		if(!$resources) $resources = $this->value();
		if (!$namespaces) $namespaces = $this->namespaces;
		$a = (!empty($namespaces))? array('ns'=> $namespaces) :  array();
		$ser = ARC2::getRDFXMLSerializer($a);
		return $ser->getSerializedIndex($resources);
		
	}
	
	function xmlencode($val)
	{
		foreach(array('<![CDATA[',']]>') as $char) $val = str_replace($char, htmlentities($char), $val);
		return '<![CDATA['.$val.']]>';
	}
	
	/**
	 * to_turtle
	 *
	 * @param array | bool $resources
	 * @param array | bool $namespaces
	 * @return string
	 * @author Keith Alexander
	 **/
	public function to_turtle($resources=false, $namespaces=false)
	{
		if(!$resources) $resources = $this->value();
		if (!$namespaces) $namespaces = $this->namespaces;
		$a = (!empty($namespaces))? array('ns'=> $namespaces) : array();
		$ser = ARC2::getTurtleSerializer($a);
		return $ser->getSerializedIndex($resources);
	}
	
	/**
	 * to_exhibit
	 *
	 * @return object
	 * @author Keith Alexander
	 * converts to an exhibit/json object
	 **/
	function to_exhibit($value=false, $language=false) 
	{
		require_once('ResourceArray.class.php');
		require_once('ExhibitArray.class.php');
		$resources = ($value)? $value : $this->value(); //the source data structure
		$exhibit = array(
					'items' => array(
									),
					'properties' => array(
						
						'label'=> array(
									'uri'=> self::RDFS.'label'
									),
						
						),
					'types' => array(
						
						'item' => array(
							'label' => 'Item',
							'pluralLabel' => 'Items',
							),
						),
					); // the target exhibit data structure
	
	
		foreach($resources as $uri => $properties)
		{
			$Resource = new ResourceArray(array($uri => $properties));
			$rdf_type =$Resource->property(self::RDF.'type');
			$rdf_type_term = URI::to_term($rdf_type);
			$exhibit['types'][$rdf_type_term] = array('label'=> URI::to_label($rdf_type), );
			$item = array(
				'id' => $uri,
				'label' => $Resource->get_title($language),
				'type' =>  ($rdf_type_term)? $rdf_type_term : 'item',
				);

			$properties = $Resource->properties();
			foreach($properties as $property => $objects)
			{
				$qname = URI::to_term($property);
				$exhibit['properties'][$qname] = array(
						'uri' => $property,
						'label' => URI::to_label($property),
					);

				$values = array();
				foreach($objects as $object)
				{
					switch(true)
					{
						default :
						case (empty($object['datatype']) AND $object['type']=='literal') :
							$valuetype = 'text';
							break;
						case (isset($resources[$object['value']]) AND $object['type']=='uri') :
							$valuetype = 'item';
							break;
						case (!isset($resources[$object['value']]) AND $object['type']=='uri') :
							$valuetype = 'url';
							break;
						case (!empty($object['datatype']) AND $object['datatype'] == self::XSD.'int') :
							$valuetype = 'number';
							break;
						case (!empty($object['datatype']) AND $object['datatype'] == self::XSD.'date') :
							$valuetype = 'date';
							break;
						case (!empty($object['datatype']) AND $object['datatype'] == self::XSD.'boolean') :
							$valuetype = 'boolean';
							break;						
					}
						$exhibit['properties'][$qname]['valueType'] = $valuetype;
						$values[]=$object['value'];

					
				}
				if(count($values)==1) $item[$qname] = $values[0];
				else $item[$qname] = $values;
				
			}
			
			$exhibit['items'][]=$item;
		}
		
		return new ExhibitArray($exhibit);
	}
	
	/**
	 * to_trix
	 *
	 * @return array
	 * @author Keith Alexander
	 **/
	function to_trix($value=false)
	{
		if(!$value) $value = $this->value();
		$xml = '';
		foreach($value as $uri => $properties)
		{
			$subject = (substr($uri,0,2)=='_:')? "<id>{$uri}</id>" : "<uri>{$uri}</uri>";
			foreach($properties as $property => $objects)
			{
				foreach($objects as $object)
				{
					if($object['type']=='literal')
					{
						if(isset($object['datatype']))
						{
							$literal = self::xmlencode($object['value']);
							$object_markedup='<typedLiteral datatype="'.$object['datatype'].'">'.$literal.'</typedLiteral>';
						}
						else
						{
							$lang = (isset($object['lang']))? ' xml:lang="'.$object['lang'].'"' : '';
						$literal = self::xmlencode($object['value']);	$object_markedup="<plainLiteral{$lang}>{$literal}</plainLiteral>";
						}
					}
					elseif($object['type']=='bnode')
					{
						$object_markedup="<id>{$object['value']}</id>";
					}
					elseif($object['type']=='uri')
					{
						$object_markedup="<uri>{$object['value']}</uri>";
					}
					else
					{
						break 2; //non-standard, so skip this triple
					}
					
					$xml.=<<<_TRIX_
					
		<triple>
			{$subject}
			<uri>{$property}</uri>
			{$object_markedup}	
		</triple>

_TRIX_;
				}
			}
		}
		
		$trix_container = <<<_TRIX_
<?xml version="1.0"?>
<TriX xmlns="http://www.w3.org/2004/03/trix/trix-1/">
	<graph>
		{$xml}
	</graph>
</TriX>
_TRIX_;

		require_once 'TRIX.class.php';
		return new TRIX($trix_container);
	}
	
	/**
	 * smush
	 *
	 * @return object
	 * @author Keith
	 **/
	function smush()
	{
		
 		$value = $this->value();
		foreach($value as $uri => $properties)
		{
			if(isset($value[$uri][self::OWL.'sameAs']))
			{
				$owlSameAsobjects = $value[$uri][self::OWL.'sameAs'];
				$props_to_transfer = $value[$uri];
				unset($value[$uri]);
				unset($props_to_transfer[self::OWL.'sameAs']);
				foreach($owlSameAsobjects as $sameAsobject)
				{

					$new_uri = $sameAsobject['value'];
					if(!isset($value[$new_uri]))
					{	
						$value[$new_uri] = $props_to_transfer;
					}
					else
					{
						foreach($props_to_transfer as $p_to_t => $os)
						{
							
							if(!isset($value[$new_uri][$p_to_t]))
							{
								$value[$new_uri][$p_to_t] = $os;
							}
							else
							{
								foreach($os as $o)
								{
									if(!in_array($o, $value[$new_uri][$p_to_t]))
									{
										$value[$new_uri][$p_to_t][]=$o;
									}
								}
							}
						}
						
					}

				}
			}
		}
		$this->set_value($value);
		
		return $this;
	}
	
	
	/**
	 * import
	 *
	 * @return array
	 * @author Keith Alexander
	 **/
	function import($input, $type=false)
	{
		if(is_string($input))
		{
			$newGraph = RDF::to_resources($input)->value();
		}
		else
		{
			$newGraph = $input;
		}
		
		$old_bnodeids = array();
		$current = $this->value();
		foreach($newGraph as $uri => $properties)
		{
			/* Make sure that bnode ids don't overlap: 
			_:a in g1 isn't the same as _:a in g2 */
			
			if(substr($uri,0,2)=='_:')//bnode
			{
				$old_id = $uri;
				$count = 1;
				
				while(isset($current[$uri]) OR 
				( $old_id!=$uri AND isset($newGraph[$uri]) )
				OR isset($old_bnodeids[$uri])
				)
				{
					$uri.=$count++;
				}
				
				if($old_id != $uri)	$old_bnodeids[$old_id] = $uri;
			}
			
			foreach($properties as $property => $objects)
			{
				foreach($objects as $object)
				{
					/* make sure that the new bnode is being used*/
					if($object['type']=='bnode')
					{
						$bnode = $object['value'];
						
						if(isset($old_bnodeids[$bnode])) $object['value'] = $old_bnodeids[$bnode];
						else //bnode hasn't been transposed
						{
								$old_bnode_id = $bnode;
								$count=1;
								while(isset($current[$bnode]) OR 
								( $object['value']!=$bnode AND isset($newGraph[$bnode]) )
								OR isset($old_bnodeids[$uri])
								)
								{
									$bnode.=$count++;
								}

								if($old_bnode_id!=$bnode)	$old_bnodeids[$old_bnode_id] = $bnode;
								$object['value'] = $bnode;
						}
					}
					
					if(!isset($current[$uri][$property]) OR !in_array($object, $current[$uri][$property]))
					{
						$current[$uri][$property][]=$object;
					}
				}
			}
			
		}
		$this->set_value($current);
		return $current;
	}
	
	
	/**
	 * filter_by_language - returns a resources array filtered by language code 
	 * - only values with a completely different language code will be removed
	 * eg: "pain au chocolat"@fr will be removed if you specify 'en-gb', but 
	 * "chocolate bread"@en-us will not
	 * @param string $lang - a language code, such as 'en-gb'
	 * @return array
	 * @author Keith Alexander
	 **/
	public function filter_by_language($lang)
	{

		$resources = $this->value();
		if(!$this->is_valid($resources)) return $this;
		$new_resources = array();
		foreach($resources as $uri => $properties)
		{
			foreach($properties as $p => $objs)
			{
				foreach($objs as $o)
				{
					if(
						empty($o['lang'])
						or
						$o['lang']=== false 
						or 
						substr($o['lang'],0,2)==substr($lang,0,2)
						)
					{
						$new_resources[$uri][$p][]=$o;
					}
				}
			}
		}
		$this_class = get_class($this);
		return new $this_class($new_resources);
	}
	
	
	
	/**
	 * remove_properties - removes specified properties from 
	 * the (optionally) specified resources
	 * @param array $del_properties an array of property uris
	 * @param array $resources
	 * @return array
	 * @author Keith Alexander
	 **/
	public function remove_properties( $del_properties, $resources=false)
	{
		$static=true;
		if(!$resources)
		{
			 $resources =& $this->value();
			 $static=false;
		}
		foreach($resources as $uri => $props)
		{
			foreach($del_properties as $dp)
			{
				unset($resources[$uri][$dp]);
			}
		}
		if(!$static) $this->set_value($resources);
		
		return $resources;
	}
	
	/**
	 * to_resource
	 * 
	 * @param string $uri
	 * @return object
	 * @author Keith Alexander
	 **/
	function to_resource($uri)
	{
		require_once('ResourceArray.class.php');
		$resources = $this->value;
		$res = new ResourceArray(array($uri=>$resources[$uri]));
		return $res;
	}
	
	/**
	 * is_valid
	 *
	 * @return boolean
	 * @author Keith
	 **/
	function is_valid($value)
	{
		if(!is_array($value)) return false;
		else
		{
			foreach($value as $uri => $properties)
			{
				
				if(is_integer($uri) OR !is_array($properties)) return false;
				
				foreach($properties as $property => $objects)
				{
					if(is_integer($property) OR !is_array($objects)) return false;
					
					foreach($objects as $object)
					{
						if(
							!is_array($object)
						OR  !isset($object['value'])
						OR  !isset($object['type'])
						OR  !is_string($object['value'])
						OR  !in_array($object['type'], array('bnode','literal','uri'))
						OR (isset($object['lang']) AND isset($object['datatype']))
						)
						{
							return false;
						}
						
					}
				}
			}
			
			return true;
		}
	}
}

?>